/*!***************************************************
 * @file: app.c
 * @brief: 本程序的总调度层
 * @author: ITHAO 
 * @date: 2018.11.19
 * @note: 
 ****************************************************/
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/ipc.h>
#include <setjmp.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/select.h>

/*应用逻辑模块头文件*/
#include "app.h"
#include "client_management_app.h"
#include "func_app.h"
/*功具性模块头文件*/
#include "cbuf.h"
#include "debug.h"
#include "comm_protocol_core.h"

extern jmp_buf ctrl_c_jump;

static void *server_work_thread(void *app);
static void *server_recv_thread(void *cbufs);
static void ctrl_c_handle(int sig);


/**
 * @brief	程序初始化部分     
 * @param	void
 * @return	初始化之后返回一个调度层结构体指针
 * @note:   -APP_PARAM_NULL            入口参数为空
 *          -APP_CREATE_PTHREAD_ERR    创建线程失败
 *          -APP_SHMGET_ERR            创建共享内存失败
 *          -APP_SHMAT_ERR             加载共享内存失败
 */
int init(struct app *p_app)
{
    int ret = 0;
    int count = 0;

    /*段错误处理初始化*/
#ifdef _CORE_DUMP_
    core_dump_init();
#endif

    if (!p_app) {
        DEBUG("PARAM NULL!\r\n");
        return (-APP_PARAM_NULL);
    }

    if (access("./log", F_OK) != 0) 
        mkdir("./log", 0777);

    /*初始化在线用户结构体*/
    ret = online_user_init(&p_app->online_user);
    if (ret < 0)
        DEBUG("client management init err. ret is %d\r\n", ret);

    /*初始化缓冲区*/
    p_app->cbufs.to_recv_pthread_fd = cbuf_alloc((sizeof(int) * 10));
    if (!p_app->cbufs.to_recv_pthread_fd)
        return (-APP_CBUF_ALLOC_ERR);
    p_app->cbufs.recv_data = cbuf_alloc((sizeof(struct data_packet) * 10));
    if (!p_app->cbufs.recv_data)
        return (-APP_CBUF_ALLOC_ERR);

    /*创建接收线程*/
    ret = pthread_create(&p_app->server_pthreads.recv_thread, NULL, 
                        server_recv_thread, &p_app->cbufs);
    if (ret != 0) {
        DEBUG("接收线程创建失败\r\n");
        return (-APP_CREATE_PTHREAD_ERR);
    }
    /*创建工作线程*/
    for (count = 0; count < MAX_WORK_THREAD_NUM; count++) {
        ret = pthread_create(&p_app->server_pthreads.work_thread[count], NULL, 
                        server_work_thread, p_app);
        if (ret != 0) {
            DEBUG("工作线程创建失败\r\n");
            return (-APP_CREATE_PTHREAD_ERR);
        }
    }

    /*设置ctrl+c信号处理函数*/
    signal(SIGINT, ctrl_c_handle);
    
    CLEAR();
    printf("服务器初始化成功！\r\n");
    printf("等待客户端接入...\r\n");
    return TRUE;
}


/**
 * @brief	程序运行部分     
 * @param	p_app--程序调度层结构体
 * @return	-APP_PARAM_NULL        入口参数为空
 *          0                       啥事也没有
 * @note:
 */
int run(struct app *p_app)
{
    int serverfd, clientfd;
    struct sockaddr_in client_addr;
    socklen_t remote_sin_size;
    struct comm_core comm;

    if (!p_app) {
        DEBUG("PARAM ERROR!\n");
        return (-APP_PARAM_NULL);
    }

    remote_sin_size = sizeof(struct sockaddr_in);

    comm_core_init(&comm);
    comm.dev.cmd = COMM_SERVER_INIT;
    serverfd = comm.ioctl(&comm.dev, 
                          comm.dev.cmd, comm.dev.arg);

    while (1) {
        clientfd = accept(serverfd, 
                          (struct sockaddr*)&client_addr, 
                          &remote_sin_size);
        if(clientfd == -1) {
            perror("accept error\r\n");
            DEBUG("ACCEPT ERROR\r\n");
            continue;
        }

        cbuf_put(p_app->cbufs.to_recv_pthread_fd, (uint8 *)&clientfd, sizeof(int));
    }

    comm_core_destory(&comm);
    return 0;
}


/**
 * @brief	程序结束部分，负责程序退出前的收尾工作    
 * @param	p_app--程序调度层结构体
 * @return	-APP_PARAM_NULL        入口参数为空
 * @note:   按下CTRL+C后会跳到这里来执行完后，程序才结束
 */
int destroy(struct app *p_app)
{
    int ret;
    struct data_packet data_packet_temp;
    int count;

    if (!p_app) {
        DEBUG("PARAM ERROR!\n");
        return (-APP_PARAM_NULL);
    }

    ret = client_management_destroy(&p_app->online_user);
    if (ret < 0)
        DEBUG("client management destroy err. ret is %d\r\n", ret);

    pthread_cancel(p_app->server_pthreads.recv_thread);
    for (count = 0; count < MAX_WORK_THREAD_NUM; count++) {
        pthread_cancel(p_app->server_pthreads.work_thread[count]);
    }

    /*清除缓冲区及其里面需要free的数据*/
    while (cbuf_len(p_app->cbufs.recv_data)) {
        reset_data_packet(&data_packet_temp);
        cbuf_get(p_app->cbufs.recv_data, 
                (uint8 *)&data_packet_temp, 
                sizeof(struct data_packet));
        free_data_packet(&data_packet_temp);
    }

    cbuf_free(p_app->cbufs.recv_data);
    cbuf_free(p_app->cbufs.to_recv_pthread_fd);

    free(p_app);

    return 0;
}

/**
 * @brief	主程序接收到fd会写入to_recv_pthread_fd里面
 *          函数收到fd后悔进行监听，收到的内容写到recv_data
 *          里面，由工作线程进行处理     
 * @param	cbufs--缓冲区，包含输入进来的clientfd缓冲区
 *                          用于输出待处理数据的data缓冲区
 * @return	-APP_PARAM_NULL        入口参数为空
 *
 * @note:
 */
static void *server_recv_thread(void *cbufs)
{
    /*buf缓冲区相关变量*/
    struct cbuf *to_recv_pthread_fd = NULL;
    struct cbuf *recv_data = NULL;
    /*fd句柄相关变量*/
    fd_set all_setfd;
    int clientfd_tmp = -1;
    int all_clientfd[MAX_SELECT_NUM], maxfd = 0;
    int clientfd_sum = 0;
    /*常规变量*/
    int count = 0, count1 = 0, show_count = 0;
    int ret = 0;
    struct timeval timeout;
    struct comm_core comm;
    struct data_with_fd data_tmp;

    if (!cbufs) {
        DEBUG("PARAM ERROR!\n");
        pthread_exit((void *)(-APP_PARAM_NULL));
    }

    /*通信协议初始化*/
    comm_core_init(&comm);
    comm.dev.cmd = COMM_RECV;
    /*缓冲区初始化*/
    to_recv_pthread_fd = ((struct cbufs*)cbufs)->to_recv_pthread_fd;
    recv_data = ((struct cbufs*)cbufs)->recv_data;
    /*select初始化*/
    FD_ZERO(&all_setfd);
    for (count = 0; count < MAX_SELECT_NUM; count++)
        all_clientfd[count] = -1;
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    reset_data_packet(&data_tmp.data); 

    while (1) {
        /*先看一看有没有fd新加入*/
        /*如果还有空位就接收新加入的fd*/
        if (clientfd_sum < MAX_SELECT_NUM) {
            /*如果缓冲区有值就一直取*/
            while ( (cbuf_len(to_recv_pthread_fd) >= sizeof(int)) ) {
                cbuf_get(to_recv_pthread_fd, (uint8 *)&clientfd_tmp, 
                         sizeof(int));
                /*遍历数组找到-1的空位*/
                for (count = 0; count < MAX_SELECT_NUM; count++) {
                    /*-1，证明空着，赋值*/
                    if (all_clientfd[count] == -1) {
                        all_clientfd[count] = clientfd_tmp;
                        //FD_SET(clientfd_tmp, &all_setfd);
                        clientfd_sum++;
                        printf("接收线程收到一个fd--%d  总连接数量--%d\r\n", 
                                clientfd_tmp, clientfd_sum);
                        if (clientfd_tmp > maxfd)
                            maxfd = clientfd_tmp;
                        clientfd_tmp = -1;
                        break;
                    }
                }
                /*到了接收fd的极限，break，不收了*/
                if (clientfd_sum == MAX_SELECT_NUM && 
                    cbuf_len(to_recv_pthread_fd) >= sizeof(int)) {
                    DEBUG("服务器繁忙，过多用户登入！\r\n");
                    break;
                }
            }
        } else
            DEBUG("服务器繁忙，过多用户登入！\r\n");

        /*select检测输入*/
        ret = select(maxfd + 1, &all_setfd, NULL, NULL, &timeout);

        if (ret < 0) {
            DEBUG("SELECT FAIL!\r\n");
            continue;
        } 
        if (ret == 0) {
            /*这里需要重新设置检测的fd和超时时间*/
            FD_ZERO(&all_setfd);
            for (count = 0; count < MAX_SELECT_NUM; count++) {
                if (all_clientfd[count] != -1)
                FD_SET(all_clientfd[count], &all_setfd);
            }
            MOVEUP(1);
            show_count++;
            if (show_count == 1)
                printf("\r\n服务器等待接收信息.  \r\n");
            if (show_count == 2)
                printf("服务器等待接收信息.. \r\n");
            if (show_count == 3) {
                printf("服务器等待接收信息...\r\n");
                MOVEUP(1);
                show_count = 0;
            }

            timeout.tv_sec = 1;
            timeout.tv_usec = 0;
            continue;    
        }
        show_count = 0;

        /*找到可以读的fd*/
        for (count = 0; count < MAX_SELECT_NUM; count++) {
            if (all_clientfd[count] != -1 &&
                FD_ISSET(all_clientfd[count], &all_setfd)) {

                comm.data.recvfd = all_clientfd[count];
                ret = comm.ioctl(&comm.dev, 
                                   comm.dev.cmd, comm.dev.arg);

                /*fd已断开连接*/
                if (ret < 0) {
                    close(all_clientfd[count]);
                    FD_CLR(all_clientfd[count], &all_setfd);
                    clientfd_sum--;
                    printf("用户fd--%d断开 总连接数量--%d\r\n",
                            all_clientfd[count], clientfd_sum);
                    data_tmp.clientfd = all_clientfd[count];
                    all_clientfd[count] = -1;
                    maxfd = 0;
                    for (count1 = 0; count1 < MAX_SELECT_NUM; count1++) {
                        if(all_clientfd[count1] > maxfd)
                            maxfd = all_clientfd[count1];
                    }
                    /*向工作线程发送注销该用户的信息*/
                    data_tmp.data.cmd_type = LOGIN_CMD_TYPE;
                    data_tmp.data.cmd = LOGOUT_BY_SERVER_CMD;
                    cbuf_put(recv_data, (uint8 *)&data_tmp, 
                             sizeof(struct data_with_fd));
                    data_tmp.clientfd = -1;
                    reset_data_packet(&data_tmp.data);
                /*有数据，则将数据写入数据缓冲区recv_data*/    
                } else {
                    cpy_data_packet(&data_tmp.data, &comm.data.recv_data_packet);
                    data_tmp.clientfd = all_clientfd[count];
                    cbuf_put(recv_data, (uint8 *)&data_tmp, 
                            sizeof(struct data_with_fd));
                    reset_data_packet(&data_tmp.data);
                    free_data_packet(&comm.data.recv_data_packet);
                }
            }/*end if*/
        }/*end for*/

        /*重新将fd加入到监听行列*/
        FD_ZERO(&all_setfd);
        for (count = 0; count < MAX_SELECT_NUM; count++) {
            if (all_clientfd[count] != -1)
                FD_SET(all_clientfd[count], &all_setfd);
        }
    }/*while*/
}

/**
 * @brief	从recv_data数据缓冲区获取需要处理的信息进行相应的处理    
 * @param	app--总结构体，里面有工作线程所需要处理的数据
 * @return	-APP_PARAM_NULL        入口参数为空
 *
 * @note:相关命令的定义在comm_protocol.h
 */
void *server_work_thread(void *app)
{
    /*buf缓冲区相关变量*/
    struct cbuf *recv_data = NULL;
    struct data_with_fd data_tmp;
    /*常规变量*/
    //int ret = 0;
    struct comm_core comm;

    if (!app) {
        DEBUG("PARAM ERROR!\n");
        pthread_exit((void *)(-APP_PARAM_NULL));
    }

    /*通信协议初始化*/
    comm_core_init(&comm);
    /*缓冲区初始化*/
    recv_data = ((struct app *)app)->cbufs.recv_data;
    reset_data_packet(&data_tmp.data);
    data_tmp.clientfd = -1;

    while (1) {
        cbuf_get(recv_data, (uint8 *)&data_tmp, 
                         sizeof(struct data_with_fd));

        
        switch (data_tmp.data.cmd_type) {
            /*处理用户管理部分的数据*/
            case LOGIN_CMD_TYPE:
                client_man_app(data_tmp.data, data_tmp.clientfd,
                               &(((struct app *)app)->online_user));
                break;

            case FRIEND_CMD_TYPE:
                friend_app(data_tmp.data, data_tmp.clientfd,
                               &(((struct app *)app)->online_user));
                break;

            case CHAT_CMD_TYPE:
                chat_app(data_tmp.data, data_tmp.clientfd,
                               &(((struct app *)app)->online_user));
                break;

            default:
                comm.dev.cmd = COMM_SEND;
                comm.data.send_data_packet.cmd_type = data_tmp.data.cmd_type;
                comm.data.send_data_packet.ack = WRONG_CMD_TYPE_ACK;
                comm.ioctl(&comm.dev, comm.dev.cmd, comm.dev.arg);
                break;

        }   

        reset_data_packet(&comm.data.send_data_packet);
        free_data_packet(&data_tmp.data);
        data_tmp.clientfd = -1;
        display_online_user(&(((struct app *)app)->online_user));
    }

}


/*Ctrl+C触发信号处理函数*/
static void ctrl_c_handle(int sig)
{
	printf("\n\tpress Ctrl+C and exit\n");
    
	longjmp(ctrl_c_jump, 1);
}

